package com.binance.healthcenter.service;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;
import org.springframework.web.client.RestTemplate;
import com.binance.healthcenter.model.HealthCheckResult;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import lombok.extern.log4j.Log4j2;

@Log4j2
@Service
public class HealthCheckService {
    @Qualifier("healthCheckRestTemplate")
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private DiscoveryClientService discoveryClientService;

    private static final ExecutorService executorService = Executors.newFixedThreadPool(20);


    public HashMap<String, Object> getHealth(String ipAddr, Integer port) {
        String healthCheckUrl = "http://" + ipAddr + ":" + String.valueOf(port) + "/health";
        log.info("HealthCheckService.getHealth :request={}", healthCheckUrl);
        HashMap<String, Object> getForObject = restTemplate.getForObject(healthCheckUrl, HashMap.class);
        log.info("HealthCheckService.getHealth :response={}", getForObject);
        return getForObject;
    }

    public HashMap<String, Object> getHealth(String healthCheckUrl) {
        log.info("HealthCheckService.getHealth :request={}", healthCheckUrl);
        HashMap<String, Object> getForObject = restTemplate.getForObject(healthCheckUrl, HashMap.class);
        log.info("HealthCheckService.getHealth :response={}", getForObject);
        return getForObject;
    }

    /**
     * 获取所有微服务的healthcheck列表
     */
    public List<HealthCheckResult> getAllHealthInfo() {
        List<ServiceInstance> serviceInstanceList = discoveryClientService.getAllServiceInstanceList();
        List<HealthCheckResult> healthCheckResultList = getHealthInfoList(serviceInstanceList);
        return healthCheckResultList;
    }

    /**
     * 获取某个微服务的healthcheck列表
     */
    public List<HealthCheckResult> getHealthInfoByServiceName(String serviceName) {
        log.info("HealthCheckService.getHealthInfoByServiceName serviceName={}",serviceName);
        List<ServiceInstance> serviceInstanceList = discoveryClientService.getServiceInstanceList(serviceName);
        List<HealthCheckResult> healthCheckResultList = getHealthInfoListWithoutExecutor(serviceInstanceList);
        return healthCheckResultList;
    }


    private List<HealthCheckResult> getHealthInfoList(List<ServiceInstance> serviceInstanceList) {
        List<HealthCheckResult> healthCheckResultList = Lists.newArrayList();
        CompletionService<HealthCheckResult> completionService =
                new ExecutorCompletionService<HealthCheckResult>(executorService);
        for (ServiceInstance serviceInstance : serviceInstanceList) {
            completionService.submit(new Callable<HealthCheckResult>() {
                @Override
                public HealthCheckResult call() throws Exception {
                    StopWatch stopWatch = new StopWatch();
                    try {
                        stopWatch.start();
                        HealthCheckResult healthCheckResult = new HealthCheckResult();
                        String healthCheckUrl = ((EurekaDiscoveryClient.EurekaServiceInstance) serviceInstance)
                                .getInstanceInfo().getHealthCheckUrl();
                        HashMap<String, Object> healthResult = getHealth(healthCheckUrl);
                        stopWatch.stop();
                        healthCheckResult.setServiceName(serviceInstance.getServiceId());
                        healthCheckResult.setHost(serviceInstance.getHost());
                        healthCheckResult.setStatus(String.valueOf(healthResult.get("status")));
                        healthCheckResult.setResultMap(healthResult);
                        healthCheckResult.setTotalTimeSecond(String.valueOf(stopWatch.getTotalTimeSeconds()));
                        return healthCheckResult;
                    } catch (Exception e) {
                        if (stopWatch.isRunning()) {
                            stopWatch.stop();
                        }
                        if(e.toString().contains("404")){
                            log.info("HealthCheckService.getHealthInfoList ignore:serviceId={},serviceHost={},Exception={}",
                                    serviceInstance.getServiceId() ,serviceInstance.getHost(), e.toString());
                          return null;
                        }
                        HashMap<String, Object> exceptionResult = Maps.newHashMap();
                        exceptionResult.put("status", "DOWN");
                        exceptionResult.put("exception", e.toString());
                        HealthCheckResult exceptionCheckResult = new HealthCheckResult();
                        exceptionCheckResult.setServiceName(serviceInstance.getServiceId());
                        exceptionCheckResult.setHost(serviceInstance.getHost());
                        exceptionCheckResult.setStatus(String.valueOf(exceptionResult.get("status")));
                        exceptionCheckResult.setResultMap(exceptionResult);
                        exceptionCheckResult.setTotalTimeSecond(String.valueOf(stopWatch.getTotalTimeSeconds()));
                        log.error("HealthCheckService.getHealthInfoList :" + serviceInstance.getServiceId() + ":"
                                + serviceInstance.getHost() + "Exception :", e);
                        return exceptionCheckResult;
                    }
                }
            });
        }
        // 获取所有结果
        for (int i = 0; i < serviceInstanceList.size(); i++) {
            try {
                Future<HealthCheckResult> future = completionService.take();
                HealthCheckResult healthCheckResult = future.get(30, TimeUnit.SECONDS);
                if(null!=healthCheckResult){
                    healthCheckResultList.add(healthCheckResult);
                }
            } catch (Exception e) {
                log.error("HealthCheckService.getHealthInfoList completionService.take() timeout", e);
            }
        }
        return healthCheckResultList;
    }


    private List<HealthCheckResult> getHealthInfoListWithoutExecutor(List<ServiceInstance> serviceInstanceList) {
        List<HealthCheckResult> healthCheckResultList = Lists.newArrayList();
        for (ServiceInstance serviceInstance : serviceInstanceList) {
            StopWatch stopWatch=new StopWatch();
            try {
                stopWatch.start();
                HealthCheckResult healthCheckResult = new HealthCheckResult();
                HashMap<String, Object> healthResult = getHealth(serviceInstance.getHost(), serviceInstance.getPort());
                stopWatch.stop();
                healthCheckResult.setServiceName(serviceInstance.getServiceId());
                healthCheckResult.setHost(serviceInstance.getHost());
                healthCheckResult.setStatus(String.valueOf(healthResult.get("status")));
                healthCheckResult.setResultMap(healthResult);
                healthCheckResult.setTotalTimeSecond(String.valueOf(stopWatch.getTotalTimeSeconds()));
                healthCheckResultList.add(healthCheckResult);
            } catch (Exception e) {
                if(stopWatch.isRunning()){
                    stopWatch.stop();
                }
                if(e.toString().contains("503")||e.toString().contains("404")){
                    log.info("HealthCheckService.getHealthInfoListWithoutExecutor  ignore" + serviceInstance.getServiceId() + ":"
                            + serviceInstance.getHost() + "Exception={} ", e.toString());
                   continue;
                }
                HashMap<String, Object> exceptionResult = Maps.newHashMap();
                exceptionResult.put("status", "DOWN");
                exceptionResult.put("exception", e.toString());
                HealthCheckResult exceptionCheckResult = new HealthCheckResult();
                exceptionCheckResult.setServiceName(serviceInstance.getServiceId());
                exceptionCheckResult.setHost(serviceInstance.getHost());
                exceptionCheckResult.setStatus(String.valueOf(exceptionResult.get("status")));
                exceptionCheckResult.setResultMap(exceptionResult);
                exceptionCheckResult.setTotalTimeSecond(String.valueOf(stopWatch.getTotalTimeSeconds()));
                healthCheckResultList.add(exceptionCheckResult);
                log.error("HealthCheckService.getHealthInfoListWithoutExecutor " + serviceInstance.getServiceId() + ":"
                        + serviceInstance.getHost() + "Exception :", e);
            }
        }
        return healthCheckResultList;
    }

    /**
     * 获取所有不健康的服务列表
     */
    public List<HealthCheckResult> getAllUnHealthInfo() {
        List<HealthCheckResult> healthCheckResultList = getAllHealthInfo();
        List<HealthCheckResult> unHealthCheckResultList = Lists.newArrayList();
        for (HealthCheckResult healthCheckResult : healthCheckResultList) {
            if ("DOWN".equalsIgnoreCase(healthCheckResult.getStatus())) {
                unHealthCheckResultList.add(filterResultMap(healthCheckResult));
            }
        }
        return unHealthCheckResultList;
    }

    private HealthCheckResult filterResultMap(HealthCheckResult healthCheckResult){
        HashMap<String, Object> resultMap=healthCheckResult.getResultMap();
        Iterator<Map.Entry<String, Object>> iterator = resultMap.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<String, Object> entry = iterator.next();
            if(String.valueOf(entry.getValue()).contains("DOWN")||entry.getKey().contains("exception")){
            }else{
                iterator.remove();
            }
        }
        healthCheckResult.setResultMap(resultMap);
        return healthCheckResult;
    }


}
